pub use std::error::Error as ErrorTrait;
use std::{borrow::Cow, fmt};

use raw_str::RawStr;

mod raw_str;

pub type BoxError = Box<Error>;
pub type Result<T, E = BoxError> = std::result::Result<T, E>;

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum ErrorType {
    NoneError,

    // Config & Params error
    InvalidError,
    ParseError,
    NotFoundError,
    Conflict,
    ImmutableVar,

    // Plugin
    ProcessHandleFailed,

    // IO error
    OpenError,
    CreateError,
    BindError,
    ListenError,
    SetSockoptError,
    AcceptError,
    ConnectError,
    HandshakeError,
    ReadError,
    WriteError,
    ReadTimedout,
    WriteTimedout,
    ReadEOF,
    WriteEOF,

    // HttpError
    HandshakeFailed,
    InvalidHttpHeader,
    HttpHeaderLarge,
    InvalidChunkedLine,
    BigHeaderLine,
    InvalidRequestLine,
    InvalidTrailers,
    InvalidResponseLine,
    H2Error,
    H2StreamClose,
    H2StreamError,
    NoReqTrailers,
    NoRespTrailers,
    NotFoundBackend,

    NotImplemented,
    InternalError,

    // custom error
    Custom(Cow<'static, str>, u32),
}

#[allow(dead_code)]
impl ErrorType {
    #[inline]
    pub fn new_custom(msg: Cow<'static, str>, code: u32) -> Self {
        ErrorType::Custom(msg, code)
    }

    #[inline]
    pub fn as_str(&self) -> &str {
        match self {
            ErrorType::NoneError => "NoneError",
            ErrorType::InvalidError => "InvalidError",
            ErrorType::ParseError => "ParseError",
            ErrorType::Conflict => "Conflict",
            ErrorType::NotFoundError => "NotFoundError",
            ErrorType::ImmutableVar => "ImmutableVar",
            ErrorType::ProcessHandleFailed => "ProcessHandleFailed",
            ErrorType::OpenError => "OpenError",
            ErrorType::CreateError => "CreateError",
            ErrorType::BindError => "BindError",
            ErrorType::ListenError => "ListenError",
            ErrorType::SetSockoptError => "SetSockoptError",
            ErrorType::AcceptError => "AcceptError",
            ErrorType::ConnectError => "ConnectError",
            ErrorType::HandshakeError => "HandshakeError",
            ErrorType::ReadError => "ReadError",
            ErrorType::WriteError => "WriteError",
            ErrorType::ReadTimedout => "ReadTimedout",
            ErrorType::WriteTimedout => "WriteTimedout",
            ErrorType::ReadEOF => "ReadEOF",
            ErrorType::WriteEOF => "WriteEOF",
            ErrorType::HandshakeFailed => "HandshakeFailed",
            ErrorType::InvalidHttpHeader => "InvalidHttpHeader",
            ErrorType::HttpHeaderLarge => "HttpHeaderLarge",
            ErrorType::BigHeaderLine => "BigHeaderLine",
            ErrorType::InvalidChunkedLine => "InvalidChunkedLine",
            ErrorType::InvalidRequestLine => "InvalidRequestLine",
            ErrorType::InvalidTrailers => "InvalidTrailers",
            ErrorType::InvalidResponseLine => "InvalidResponseLine",
            ErrorType::H2Error => "H2Error",
            ErrorType::H2StreamClose => "H2StreamClose",
            ErrorType::H2StreamError => "H2StreamError",
            ErrorType::NoReqTrailers => "NoReqTrailers",
            ErrorType::NoRespTrailers => "NoRespTrailers",
            ErrorType::NotFoundBackend => "NotFoundBackend",
            ErrorType::NotImplemented => "NotImplemented",
            ErrorType::InternalError => "InternalError",
            ErrorType::Custom(err, _) => err,
        }
    }
}

#[derive(Debug)]
pub struct Error {
    pub etype: ErrorType,
    pub msg: Option<RawStr>,
    pub inner: Option<Box<dyn ErrorTrait + Send + Sync>>,
}

#[allow(dead_code)]
impl Error {
    #[inline]
    pub fn new(etype: ErrorType) -> BoxError {
        Box::new(Self {
            etype,
            msg: None,
            inner: None,
        })
    }

    #[inline]
    pub fn new_custom(msg: Cow<'static, str>, code: u32) -> BoxError {
        Box::new(Self {
            etype: ErrorType::Custom(msg, code),
            inner: None,
            msg: None,
        })
    }

    #[inline]
    pub fn from_msg<S>(err_type: ErrorType, msg: S) -> BoxError
    where
        S: Into<RawStr>,
    {
        let mut err = Self::new(err_type);
        err.msg = Some(msg.into());
        err
    }

    #[inline]
    pub fn from_error<S, E>(err_type: ErrorType, msg: S, e: E) -> BoxError
    where
        S: Into<RawStr>,
        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
    {
        let mut err = Self::new(err_type);
        err.msg = Some(msg.into());
        err.inner = Some(e.into());
        err
    }

    #[inline]
    pub fn error_type(&self) -> &ErrorType {
        &self.etype
    }

    #[inline]
    pub fn into_result<T>(self: BoxError) -> Result<T> {
        Err(self)
    }

    #[inline]
    pub fn e_explain<T, S: Into<RawStr>>(e: ErrorType, msg: S) -> Result<T> {
        Err(Self::from_msg(e, msg))
    }

    pub fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.etype {
            ErrorType::Custom(err, code) => {
                write!(f, "{}-{}", err, code)?;
            }
            _ => {
                write!(f, "{}", self.etype.as_str())?;
            }
        }

        if let Some(msg) = &self.msg {
            write!(f, ", msg: {}", msg.as_str())?;
        }

        if let Some(inner) = &self.inner {
            write!(f, ", cause: [{}]", inner)?;
        }

        Ok(())
    }
}

impl fmt::Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.display(f)
    }
}

impl ErrorTrait for Error {}

#[allow(dead_code)]
pub trait IfNone<T, S> {
    fn if_none(self, err_type: ErrorType, msg: S) -> Result<T, BoxError>
    where
        S: Into<raw_str::RawStr>;
}

impl<T, S> IfNone<T, S> for Option<T> {
    #[inline]
    fn if_none(self, err_type: ErrorType, msg: S) -> Result<T, BoxError>
    where
        S: Into<raw_str::RawStr>,
    {
        if let Some(t) = self {
            Ok(t)
        } else {
            Err(Error::from_msg(err_type, msg))
        }
    }
}

// try transfer any rst into our Result
#[allow(dead_code)]
pub trait IfErr<T, E, S> {
    fn if_err(self, err_type: ErrorType, msg: S) -> Result<T, BoxError>
    where
        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
        S: Into<raw_str::RawStr>;
}

impl<T, E, S> IfErr<T, E, S> for Result<T, E> {
    #[inline]
    fn if_err(self, err_type: ErrorType, msg: S) -> Result<T, BoxError>
    where
        E: Into<Box<dyn ErrorTrait + Send + Sync>>,
        S: Into<raw_str::RawStr>,
    {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(Error::from_error(err_type, msg, e)),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_error() {
        let err = Error::new(ErrorType::OpenError);
        assert_eq!(format!("err is {}", err), "err is OpenError");

        let err = Error::new_custom("Custom01".into(), 1);
        assert_eq!(format!("err is {}", err), "err is Custom01-1");

        let err = Error::new_custom("Custom02".into(), 1);
        assert_eq!(format!("err is {}", err), "err is Custom02-1");

        let err =
            Error::from_msg(ErrorType::OpenError, "while opening xxx.conf");
        assert_eq!(
            format!("{}", err),
            "OpenError, msg: while opening xxx.conf"
        );

        let none: Option<i32> = None;
        let none_err_str = none
            .if_none(ErrorType::NoneError, "none")
            .unwrap_err()
            .to_string();

        assert_eq!(none_err_str, "NoneError, msg: none");

        let res: Result<String, String> =
            Result::Err("open file failed".to_string());
        let res_err_str = res
            .if_err(ErrorType::OpenError, "while opening xxx.conf")
            .unwrap_err()
            .to_string();
        assert_eq!(
            res_err_str,
            "OpenError, msg: while opening xxx.conf, cause: [open file failed]"
        );
    }
}
